<?php
/**
 * @file DsCahce.class.php
 * @author LD (kldscs[at]gmail.com)
 * @date 2009-11-25 15:48:13
 * @desc 
 *   PHP的简单缓存(只能保存变量,避免序列化)
 *
 * ChangeLog: 
 * Sat Dec  5 00:16:20 CST 2009
 *     完美版本的 DsCache 1.0
 * 
 */
class DsCache{

	/**
	 * 默认的缓存策略
	 *
	 * DSCACHE_LIFE_TIME - life_time: 缓存有效时间（秒），默认值 900
     *    如果设置为 0 表示缓存总是失效，设置为 null 则表示不检查缓存有效期。
	 *
	 * DSCACHE_ENCODING - encoding_filename： 编码缓存文件名，默认为 true
	 *    如果缓存ID存在非文件名字符，那么必须对缓存文件名编码。
     *
     * DSCACHE_DIR - cache_dir: 缓存目录（必须指定）
     * 
	 * DSCACHE_SUBDIR - cache_subdir: 缓存子目录名，默认为null
	 *    如果不为空，则会在缓存目录下创建子目录保存缓存文件。
	 *    (Linux下单目录建议不要放置超过1000个文明)
     *
	 * DSCACHE_UMASK - cache_dir_umask: 创建缓存目录时的标志，默认为 0700
     *
	 * @var array
	 */
	private $_default_policy = array(
		'life_time'         => 900,
		'encoding_filename' => true,
		'cache_dir'         => null,
		'cache_subdir'   	=> null,
		'cache_dir_umask'   => 0700,
	);
	
	/**
	 * 构造函数
	 *
	 * @param 默认的缓存策略 $default_policy
	 */
	public function __construct(array $default_policy = null){
		if (!is_null($default_policy)){
			$this->_default_policy = array_merge($this->_default_policy, $default_policy);
        }else{
            die("You should fixed an cache directory at least!");
        }
	}
	
	/**
     * 写入缓存
     *   缓存时间为空, 返回 false
     *
     * @param string $id
     * @param mixed $data
     * @param array $policy
     */
    public function set($id, $data, array $policy = null){
        $policy = $this->_policy($policy);
        if( empty($policy['life_time']) ) return false;
        
        $path = $this->_path($id, $policy);
        $content = array(
            'expired' => time() + $policy['life_time'],
            'data'    => $data,
        );
        $content = '<?php return ' . var_export($content, true) . ';';

        file_put_contents($path, $content, LOCK_EX);
        file_put_contents($path, php_strip_whitespace($path), LOCK_EX);
        clearstatcache();
        return true;
    }
	
    /**
     * 读取缓存，失败或缓存撒失效时返回 false
     *   缓存时间为空, 返回 false
     *   缓存时间为负, 直接返回缓存变量
     *
     * @param string $id
     * @param array $policy
     *
     * @return mixed
     */
    public function get($id, array $policy = null){
        $policy = $this->_policy($policy);
        if( empty($policy['life_time']) ) return false;
        
        $path = $this->_path($id, $policy);
        if (!is_file($path)) { return false; }

        $data = include($path);
        if (!is_array($data) || !isset($data['expired'])) return false;

        if ($policy['life_time'] < 0){
            return $data['data'];
        }else{
            return ($data['expired'] > time()) ? $data['data'] : false;
        }
    }

	/**
	 * 删除指定的缓存
	 *
	 * @param string $id
	 * @param array $policy
	 */
	public function remove($id, array $policy = null){
		$path = $this->_path($id, $this->_policy($policy));
		if (is_file($path)) { unlink($path); }
	}
	
	/**
	 * 删除所有的缓存
	 *
	 * @param array $policy
	 */
	public function removeAll(array $policy = null){
		$policy = $this->_policy($policy);
    	$this->delete($policy['cache_dir'],true);
	}
	
	/**
	 * 清空文件夹
	 * 
	 * @param string $path
	 * @param boolean $is_root
	 */
	public function delete($path,$is_root=false){
		if(is_dir($path)){
			if($is_root) $cycles = true;
			elseif( !@rmdir($path) ) $cycles = true;
			else $cycles = false;
			
			if ( $cycles ) {
				if ( $dp = opendir($path) ) {  
                    while( ($file=readdir($dp)) != false ) {

                        if($file!='.' and $file!='..'){
                            $file = $path.DIRECTORY_SEPARATOR.$file;
                            if ( is_dir($file) ) {
                                $this->delete( $file );
                             } else {
					            @unlink( $file );
                             }
                        }//. ..
					}
                    closedir($dp);
                    if( !$is_root ) rmdir($path); 
				}else{
					die("Can't open directory: {$path}");
				}
			}//all ready remove dir
		}else unlink($path);
	}
	
    /**
     * 确定缓存文件名
     *
     * @param string $id
     * @param array $policy
     *
     * @return string
     */
    protected function _path($id, array $policy){
    	if ($policy['encoding_filename']){
			$filename = 'DsCache_' . md5($id) . '.php';
		}else{
			$filename = 'DsCache_' . preg_replace("/[[:punct:]]|[[:space:]]/", '',  $id) . '.php';
		}	
		
        $root_dir = rtrim($policy['cache_dir'], '\\/');
        if( !empty($policy['cache_subdir']) ){
            $real_dir = $root_dir.DIRECTORY_SEPARATOR.$policy['cache_subdir'];
            if( is_dir($real_dir)  ){
                $root_dir = rtrim($real_dir, '\\/');
            }else
              foreach( preg_split('/\/|\\\/', $policy['cache_subdir'], -1,PREG_SPLIT_NO_EMPTY) as $d ){
				if(!empty($d)){
					$root_dir .= DIRECTORY_SEPARATOR.$d;
					@mkdir($root_dir, $policy['cache_dir_umask']);
				}
			  }
		}
		
        return $root_dir . DIRECTORY_SEPARATOR . $filename;
    }

    /**
     * 返回有效的策略选项
     *
     * @param array $policy
     * @return array
     */
    protected function _policy(array $policy = null){
    	$my_policy = is_null($policy) ? $this->_default_policy : array_merge($this->_default_policy, $policy);
    	if(!is_dir($my_policy['cache_dir']) OR !is_writable($my_policy['cache_dir'])){
    		die("Please Fix Cache Directory({$my_policy['cache_dir']})!");
    	}
        return $my_policy;
    }
}//END class DsCache
?>
